package com.xlabs.server.DO;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Set;

import javax.jdo.PersistenceManager;
import javax.jdo.Query;
import javax.jdo.annotations.Extension;
import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.IdentityType;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;
import javax.jdo.annotations.Version;
import javax.jdo.annotations.VersionStrategy;

import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.appengine.api.users.User;
import com.xlabs.client.DTO.QuestionDTO;
import com.xlabs.client.DTO.TransferDTO;

/**
 * Question data object.
 */
@PersistenceCapable(identityType = IdentityType.APPLICATION)
@Version(
		strategy=VersionStrategy.VERSION_NUMBER, 
		column = "version",
		extensions={@Extension(vendorName="datanucleus", key="field-name", value="version")})
public class Question
{
	/**
	 * Primary key.
	 */
	@PrimaryKey
	@Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
	private Key key;
	
	/**
	 * JDO populated version.
	 */
	private Integer version;

	/**
	 * The text comment.
	 */
	@Persistent
	private String questionText;

	/**
	 * The start time of video to which this question relates in seconds.
	 */
	@Persistent
	private Integer startTime;

	/**
	 * The end time of video to which this question relates.
	 */
	@Persistent
	private Integer endTime;

	/**
	 * The video that has this comment.
	 */
	@Persistent
	private Key video;

	/**
	 * Replies to this comment.
	 */
	@Persistent(mappedBy = "question")
	private List<Reply> replies;

	/**
	 * When created.
	 */
	@Persistent
	private Date createdOn;

	/**
	 * Created by user.
	 */
	@Persistent
	private User createdBy;
	
	/**
	 * Users who flagged this question.
	 */
	@Persistent
	private Set<User> flaggedBy;
	
	/**
	 * The reply that is chosen as answer for this question.
	 */
	@Persistent
	private Key answer;
	
	/**
	 * Index for sorting based on question's start time.
	 */
	@Persistent
	private String startTimeIndex;

	/**
	 * Initializes an instance of TimedComment.
	 * 
	 * @param questionText The comment.
	 * @param startTime the start of corresponding video interval.
	 * @param endTime  the end of corresponding video interval.
	 * @param user User that added this question
	 */
	public Question(String questionText, int startTime, int endTime, Date createdOn, User user)
	{
		this.questionText = questionText;
		this.startTime = startTime;
		this.endTime = endTime;
		this.createdOn = createdOn;
		this.createdBy = user;
		this.startTimeIndex = ((leftPad(startTime, 6) + "") + "|" + createdOn + "|" + user.getEmail()); 
	}

	/**
	 * Adds padding 0 to the left of the string representation to make 6 digits.
	 * @param n
	 * @param digits number of digits.
	 * @return
	 */
	private String leftPad(int n, int digits)
	{
		String result = String.valueOf(n);
		int zerosToAdd = digits - result.length();
		
		for (int nIdx = 0; nIdx < zerosToAdd; nIdx ++)
		{
			result = "0" + result;
		}
		
		return result;
	}
	
	/**
	 * Used by RequestFactory.
	 * @return
	 */
	public Integer getVersion()
	{
		return this.version;
	}
	
	/**
	 * Setter for {@link #version}
	 * @param version 
	 * @return
	 */
	public void setVersion(Integer version)
	{
		this.version = version;
	}

	/**
	 * Gets the id of this question.
	 * @return Encoded key in string.
	 */
	public String getId()
	{
		return KeyFactory.keyToString(key);
	}

	/**
	 * Gets the key.
	 * 
	 * @return id.
	 */
	public Key getKey()
	{
		return key;
	}
	
	/**
	 * Sets the key.
	 * 
	 * @param key
	 *            key.
	 */
	public void setKey(Key key)
	{
		this.key = key;
	}

	/**
	 * Gets comment.
	 * 
	 * @return comment.
	 */
	public String getQuestionText()
	{
		return questionText;
	}

	/**
	 * Sets Comment.
	 * 
	 * @param questionText
	 *            The text comment.
	 */
	public void setQuestionText(String questionText)
	{
		this.questionText = questionText;
	}

	/**
	 * Gets video.
	 * 
	 * @return video.
	 */
	public Key getVideo()
	{
		return video;
	}

	/**
	 * Sets video
	 * 
	 * @param video
	 *            video that has this comment.
	 */
	public void setVideo(Key video)
	{
		this.video = video;
	}

	/**
	 * Gets replies.
	 * 
	 * @return List of replies.
	 */
	public List<Reply> getReplies()
	{
		return replies;
	}

	/**
	 * Sets replies.
	 * 
	 * @param replies
	 *            List of replies.
	 */
	public void setReplies(List<Reply> replies)
	{
		this.replies = replies;
	}

	/**
	 * Adds a reply.
	 * 
	 * @param reply
	 *            reply 
	 */
	public void addReply(Reply reply)
	{
		if (this.replies == null)
		{
			this.replies = new ArrayList<Reply>();
		}
		this.replies.add(reply);
	}

	/**
	 * Returns the encoded Key of this JDO as a string.
	 * 
	 * @return encoded key.
	 */
	public String getEncodedKey()
	{
		return KeyFactory.keyToString(this.key);
	}
	
	/**
	 * Necessary method for the RequestFactory.
	 * @param id The encoded key of the question.
	 * @return The question with the given id.
	 */
	public static Question findQuestion(String id)
	{
		if (id == null)
		{
			return null;
		}
		
    	PersistenceManager pm = PMF.get().getPersistenceManager();
    	try
    	{
    		Question question = pm.getObjectById(Question.class, id);
    		return question;
    	}
    	finally
    	{
    		pm.close();
    	}
	}
	
	/**
	 * Persist method used for RequesyFactory.
	 */
	public void persist() 
	{
		PersistenceManager pm = PMF.get().getPersistenceManager();
		try
		{
			pm.makePersistent(this);
		}
		finally
		{
			pm.close();
		}
	}
	
	/**
	 * remove method used for RequesyFactory
	 */
	public void remove() 
	{
		PersistenceManager pm = PMF.get().getPersistenceManager();
		try
		{
			pm.deletePersistent(this);
		}
		finally
		{
			pm.close();
		}
	}
	
	/**
	 * Gets questions asked based on start time index for a video.
	 * @param videoId given video id.
	 * @param startTimeIndexIncl interval start.
	 * @param endTimeIndexExcl interval end.
	 * @param direction specifies the direction of query.  
	 * @param pageSize how many questions to return. 
	 * @return transfer object including the questions.
	 */
    public static List<Question> getQuestionsByStartTimeIndex(
    		String videoId, 
    		String startTimeIndexIncl, 
    		String endTimeIndexExcl, 
    		int direction, 
    		int pageSize)
	{

		PersistenceManager pm = PMF.get().getPersistenceManager();
		
    	Video.AddVideo(videoId);

    	try
		{
    		QueryResult<List<Question>> queryResult = 
    			(QueryResult<List<Question>>) Question.queryQuestionsByStartTimeIndex(startTimeIndexIncl, endTimeIndexExcl, direction, pageSize + 1, pm);
    		
    		queryResult.getResults().size();
    		List<Question> questions = queryResult.getResults();
    		if (direction == TransferDTO.LEFT_DIRECTION) 
    		{
    			questions = new ArrayList<Question>(questions);
    			Collections.reverse(questions);
    		}
    		
    		return questions;
		} 
		finally
		{
			pm.close();
		}

	}
	
	/**
	 * Gets a page of questions that are asked in the given time index interval.
	 * 
	 * @param startTimeIndexIncl
	 *            start time index.
	 * @param endTimeIndexExcl
	 *            end time index.
	 * @param direction
	 *            specifies the direction of query.
	 * @param pageSize
	 *            Size of a page.
	 * @param pm
	 *            persistence manager
	 * @return Object array including list of questions and query cursor.
	 */
	@SuppressWarnings("unchecked")
	public static QueryResult<List<Question>> queryQuestionsByStartTimeIndex(
			String startTimeIndexIncl, 
			String endTimeIndexExcl, 
			int direction, 
			int pageSize,
			PersistenceManager pm)
	{
		String questionsQuery = "SELECT FROM "
				+ Question.class.getName()
				+ " where startTimeIndex >= :intervalStart && startTimeIndex < :intervalEnd"
				+ " ORDER BY startTimeIndex "
				+ (direction == TransferDTO.RIGHT_DIRECTION ? " ASC " : " DESC ");

		Query query = pm.newQuery(questionsQuery);
		query.setRange(0, pageSize);

		List<Question> questionList = (List<Question>) query.execute(startTimeIndexIncl, endTimeIndexExcl);
		
		return new QueryResult<List<Question>>(questionList);
	}

	/**
	 * Gets createdOn.
	 * 
	 * @return time of creation.
	 */
	public Date getCreatedOn()
	{
		return createdOn;
	}

	/**
	 * Gets createdBy.
	 * 
	 * @return creator.
	 */
	public User getCreatedBy()
	{
		return createdBy;
	}
	
	/**
	 * Gets the name of the creator.
	 * @return
	 */
	public String getCreatedByName()
	{
		return createdBy.getNickname();
	}

	/**
	 * Gets {@link #startTime}
	 * 
	 * @return {@link #startTime}
	 */
	public Integer getStartTime()
	{
		return startTime;
	}

	/**
	 * Gets {@link #endTime}
	 * 
	 * @return {@link #endTime}
	 */
	public Integer getEndTime()
	{
		return endTime;
	}

	/**
	 * Converts an array of {@link com.xlabs.server.DO.Question} objects to an
	 * array of {@link QuestionDTO}
	 * 
	 * @param questionDOList
	 * @return
	 */
	@Deprecated
	public static QuestionDTO[] toQuestionDTOArray(
			List<Question> questionDOList)
	{
		QuestionDTO[] questionDTOs = new QuestionDTO[questionDOList.size()];

		int index = 0;
		for (Question aQuestionDO : questionDOList)
		{
			questionDTOs[index++] = aQuestionDO.toQuestionDTO();
		}

		return questionDTOs;
	}

	public static List<QuestionDTO> toDTOList(List<Question> doList)
	{
		List<QuestionDTO> dtoList = new ArrayList<QuestionDTO>();

		for (Question questionDO : doList)
		{
			dtoList.add(questionDO.toQuestionDTO());
		}

		return dtoList;
	}

	/**
	 * Converts this object to CommentDTO.
	 * 
	 * @return com.xlabs.client.CommentDTO
	 */
	public QuestionDTO toQuestionDTO()
	{
		QuestionDTO dto = new QuestionDTO(getQuestionText(), getStartTime(), getEndTime(), getCreatedOn(), getStartTimeIndex(), getAnswer() != null);

		dto.setEncodedKey(this.getEncodedKey());
		dto.setCreatedBy(this.getCreatedBy().getNickname());

		return dto;
	}
	
	/**
	 * see {@link Object}{@link #toString()}
	 */
	public String toString()
	{
		return "startTime : " + startTime + " questionText: " + questionText;
		
	}

	/**
	 * Getter for {@link #startTimeIndex}
	 * @return the {@link #startTimeIndex}
	 */
	public String getStartTimeIndex() 
	{
		return startTimeIndex;
	}

	/**
	 * Setter for {@link #answer}.
	 * @param answer
	 */
	public void setAnswer(Key answer)
	{
		this.answer = answer;
	}

	/**
	 * Getter for {@link #answer}
	 * @return
	 */
	public Key getAnswer()
	{
		return answer;
	}
	
	/**
	 * Sets the answer key from string.
	 * @param id
	 */
	public void setAnswerId(String id)
	{
		this.answer = id != null ? KeyFactory.stringToKey(id) : null;
	}
	
	/**
	 * Get the answer id.
	 * @return
	 */
	public String getAnswerId()
	{
		return this.answer == null ? null : KeyFactory.keyToString(this.answer);
	}

	/**
	 * Is the question answered ?
	 * @return
	 */
	public Boolean getAnswered()
	{
		return this.answer != null;
	}

	/**
	 * Setter for {@link #flaggedBy}
	 * @param flaggedBy the flaggedBy to set
	 */
	public void setFlaggedBy(Set<User> flaggedBy)
	{
		this.flaggedBy = flaggedBy;
	}

	/**
	 * Returns users who flagged this questions.
	 * @return the flaggedBy
	 */
	public Set<User> getFlaggedBy()
	{
		return flaggedBy;
	}
}
